package net.anquankong.websec.argus;

import java.net.*;
import java.io.*;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class ClientControlServiceThread extends Thread {
	public enum ClientControlServiceThreadStates {
		UNKNOWN, WAITING_FOR_HELLO, WAITING_FOR_PARAMS, WAITING_FOR_START, RUNNING, WAITING_FOR_QUIT
	}

	// Private vars
	private Socket clientSocket_ = null;
	private int clientId_ = 0;
	private InetAddress dest_;
	private int port_;
	private Boolean runThread_ = true;
	private ClientControlServiceThreadStates currentState_ = ClientControlServiceThreadStates.UNKNOWN;
	private WebClientThread webClientThread_ = null;
	private String tmpFolder_ = "";

	// Constants
	private static String rspBadCommand = "400 BAD COMMAND";
	private static String rspSuccess = "200 SUCCESS";
	private static String rspFinished = "201 FINISHED";
	// rspHello = "100 ARGUS_PIPE_clientId";

	// Params
	private String urlStr_ = "";
	private Boolean enableJavaScript_ = true;
	private Boolean enableFlashParser_ = true;
	private Boolean outputAST_ = true;
	private Boolean outputAnchors_ = true;
	private Boolean outputText_ = true;
	private Boolean outputObjects_ = false;

	private static Log log_ = LogFactory
			.getLog(ClientControlServiceThread.class);

	private String getPipePrefix() {
		return "ARGUS_PIPE_" + Integer.toString(clientId_);
	}

	ClientControlServiceThread(Socket s, int clientId, String tmpFolder) {
		clientSocket_ = s;
		clientId_ = clientId;
		tmpFolder_ = tmpFolder;

		port_ = 23013;
		dest_ = s.getInetAddress();
	}

	public void run() {
		BufferedReader in = null;
		PrintWriter out = null;

		log_.debug("Accepted Client : ID - " + clientId_ + " : Address - "
				+ clientSocket_.getInetAddress().getHostAddress());

		// Set current state
		currentState_ = ClientControlServiceThreadStates.WAITING_FOR_HELLO;

		// Set read timeout
		try {
			clientSocket_.setSoTimeout(15 * 60 * 1000);
		} catch (SocketException e1) {
			// TODO Auto-generated catch block
			return;
		}

		try {
			in = new BufferedReader(new InputStreamReader(
					clientSocket_.getInputStream(), "UTF8"));
			out = new PrintWriter(new OutputStreamWriter(
					clientSocket_.getOutputStream(), "UTF8"));

			boolean isBadCommand = false;
			boolean resultSent = false;
			String clientCommand = "";
			String clientControlWord = "";
			String clientParams = "";

			while (runThread_) {
				clientCommand = in.readLine();
				log_.debug("Client command: " + clientCommand);

				// Assume the command is bad
				isBadCommand = true;

				// Parse clientCommand
				int i = clientCommand.indexOf(' ');
				if (i != -1) {
					clientControlWord = clientCommand.substring(0, i);
					clientParams = clientCommand.substring(i + 1);
				} else {
					clientControlWord = clientCommand;
					clientParams = "";
				}

				// Accept 'hello', return pipe prefix
				if (currentState_ == ClientControlServiceThreadStates.WAITING_FOR_HELLO) {
					if (clientControlWord.compareTo("hello") == 0) {
						out.println("100 " + this.getPipePrefix());
						currentState_ = ClientControlServiceThreadStates.WAITING_FOR_PARAMS;
						isBadCommand = false;
					}
				}
				// Accept params, until 'setUrl'
				else if (currentState_ == ClientControlServiceThreadStates.WAITING_FOR_PARAMS) {
					if (clientControlWord.compareToIgnoreCase("setParam") == 0) {
						// Parse params
						int j = clientParams.indexOf('=');
						if (j != -1) {
							String paramName = clientParams.substring(0, j);
							String paramValue = clientParams.substring(j + 1);

							// For each param...
							if (paramName
									.compareToIgnoreCase("enableJavaScript") == 0) {
								if (paramValue.compareToIgnoreCase("true") == 0) {
									enableJavaScript_ = true;
									out.println(rspSuccess);
									isBadCommand = false;
								} else if (paramValue
										.compareToIgnoreCase("false") == 0) {
									enableJavaScript_ = false;
									out.println(rspSuccess);
									isBadCommand = false;
								}
							} else if (paramName
									.compareToIgnoreCase("enableFlashParser") == 0) {
								if (paramValue.compareToIgnoreCase("true") == 0) {
									enableFlashParser_ = true;
									out.println(rspSuccess);
									isBadCommand = false;
								} else if (paramValue
										.compareToIgnoreCase("false") == 0) {
									enableFlashParser_ = false;
									out.println(rspSuccess);
									isBadCommand = false;
								}
							} else if (paramName
									.compareToIgnoreCase("outputObjects") == 0) {
								if (paramValue.compareToIgnoreCase("true") == 0) {
									outputObjects_ = true;
									out.println(rspSuccess);
									isBadCommand = false;
								} else if (paramValue
										.compareToIgnoreCase("false") == 0) {
									outputObjects_ = false;
									out.println(rspSuccess);
									isBadCommand = false;
								}
							}
						}
					} else if (clientControlWord.compareToIgnoreCase("setUrl") == 0) {
						try {
							URI url = new URI(clientParams).normalize();
							urlStr_ = url.toString();
							isBadCommand = false;
						} catch (Exception e) {
						}

						if (!isBadCommand) {
							out.println(rspSuccess);
							currentState_ = ClientControlServiceThreadStates.WAITING_FOR_START;
						}
					} else if (clientControlWord.compareToIgnoreCase("setDest") == 0) {
						try {
							int j = clientParams.indexOf(':');
							if (j != -1) {
								String destIPStr = clientParams.substring(0, j);
								String destPortStr = clientParams
										.substring(j + 1);

								dest_ = InetAddress.getByName(destIPStr);
								port_ = Integer.valueOf(destPortStr);
							}

							isBadCommand = false;
						} catch (Exception e) {
							// TODO Auto-generated catch block
						}

						if (!isBadCommand) {
							out.println(rspSuccess);
						}
					}
				}
				// Accept 'start'
				else if (currentState_ == ClientControlServiceThreadStates.WAITING_FOR_START) {
					if (clientControlWord.compareToIgnoreCase("start") == 0) {
						// TODO: start WebClient thread here
						// webClientThread_ = new
						// WebClientThread(getPipePrefix(),
						// urlStr_, enableJavaScript_, enableFlashParser_,
						// tmpFolder_ + getPipePrefix() + "\\");
						// ArgusContext argusContext = new ArgusContext();
						// argusContext.dest = this.dest_;
						// argusContext.port = this.port_;
						// argusContext.pipeIdx = this.clientId_;
						webClientThread_ = new WebClientThread(/* argusContext, */
						urlStr_, enableJavaScript_, enableFlashParser_,
								tmpFolder_ + getPipePrefix() + "\\");
						webClientThread_.start();

						out.println(rspSuccess);
						currentState_ = ClientControlServiceThreadStates.RUNNING;
						isBadCommand = false;
					}
				}
				// Accept 'stop'
				else if (currentState_ == ClientControlServiceThreadStates.RUNNING) {
					if (clientControlWord.compareToIgnoreCase("stop") == 0) {
						// TODO: stop WebClient thread here
						webClientThread_.interrupt();

						out.println(rspSuccess);
						currentState_ = ClientControlServiceThreadStates.WAITING_FOR_QUIT;
						isBadCommand = false;

						// TODO: output result if resultSent == false
						if (!resultSent) {
							out.print(webClientThread_.serializeResults(
									outputAnchors_, outputAST_, outputText_,
									outputObjects_));
						}
					} else if (clientControlWord
							.compareToIgnoreCase("isFinished") == 0) {
						if (webClientThread_.isFinished() && (!resultSent)) {
							// Output result here
							out.println(rspFinished);
							out.print(webClientThread_.serializeResults(
									outputAnchors_, outputAST_, outputText_,
									outputObjects_));
							resultSent = true;
							isBadCommand = false;
						} else {
							out.println(rspSuccess);
							isBadCommand = false;
						}
					}
				}
				// Accept 'quit'
				else if (currentState_ == ClientControlServiceThreadStates.WAITING_FOR_QUIT) {
					if (clientControlWord.compareToIgnoreCase("quit") == 0) {
						// quit
						runThread_ = false;

						out.println(rspSuccess);
						currentState_ = ClientControlServiceThreadStates.UNKNOWN;
						isBadCommand = false;
					}
				}
				// Wrong state here? Should never reach this branch
				else {
				}

				if (isBadCommand) {
					out.println(rspBadCommand);
				}

				out.flush();
			}
		} catch (Exception e) {
			log_.error("Exception thrown on control channel. Client: ID - "
					+ clientId_ + " : Address - "
					+ clientSocket_.getInetAddress().getHostAddress());
			log_.error("Exception message - " + e.toString());
		} finally {
			// Clean up
			try {
				in.close();
				out.close();
			} catch (IOException e) {
				log_.error("Exception thrown on control channel. Client: ID - "
						+ clientId_ + " : Address - "
						+ clientSocket_.getInetAddress().getHostAddress());
				log_.error("Exception message - " + e.toString());
			}

			try {
				clientSocket_.close();
			} catch (IOException e) {
				log_.error("Exception thrown on control channel upon socket closure. Client: ID - "
						+ clientId_);
				log_.error("Exception message - " + e.toString());
			}
		}
	}
}
